﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Tensop.Net.Tcp
{
    public class TcpClient : IDisposable
    {
        //
        //

        private Socket          m_Client;
        private bool            m_Connected;
        private bool            m_lockConnected;
        private Task            m_Deamon;
        private IPEndPoint      m_RemotedEndPoint;
        private int             m_ContentLength;
        private byte            m_Protocol;
        private byte            m_Version;
        private byte[]          m_needSend;

        private SocketAsyncEventArgs         m_Recv;
        private NetworkStream                m_Send;
        private NetworkStream                m_Read;
        private List<byte>                   m_Buffer;

        public event ConnectStateChangedEventHandler           ConnectStateChanged;
        public event ClientReceiveCompletedEventHandler        ReceiveCompleted;

        //
        // 构造TCPClient函数
        //

        public TcpClient()
        {
            m_Client = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
            m_Deamon = new Task( Deamon );

            m_lockConnected = false;
            m_Connected = false;
            m_RemotedEndPoint = m_Client.IsBound ? (IPEndPoint) m_Client.LocalEndPoint : new IPEndPoint( IPAddress.None, 0 );

            m_Deamon.Start();
            m_ContentLength = -1;
            m_Buffer = new List<byte>( 65536 );
        }

        //
        // 异步连接服务器
        //

        public void Connect(IPEndPoint server)
        {
            m_RemotedEndPoint = server;
            if (m_lockConnected)
                throw new ConnectException();
            var Connector = new SocketAsyncEventArgs();
            Connector.RemoteEndPoint = server;
            Connector.Completed += IO_Completed;

            m_lockConnected = true;
            if (!m_Client.ConnectAsync( Connector ))
            {
                Task Connect = new Task( () => {
                    try
                    {
                        m_Client.Connect( m_RemotedEndPoint );
                        StartConnect();

                        m_Recv = new SocketAsyncEventArgs();
                        m_Recv.SetBuffer( new byte[ 65536 ], 0, 65536 );
                        m_Recv.Completed += IO_Completed;

                        m_Send = new NetworkStream( m_Client );
                        m_Read = new NetworkStream( m_Client );
                        StartReceive();
                    }
                    catch
                    {
                        EndConnect();
                    }
                } );
                Connect.Start();
            }

        }

        private void EndConnect(SocketAsyncEventArgs e)
        {


            m_Recv = new SocketAsyncEventArgs();
            m_Recv.SetBuffer( new byte[ 65536 ], 0, 65536 );
            m_Recv.Completed += IO_Completed;

            m_Send = new NetworkStream( m_Client );
            m_Read = new NetworkStream( m_Client );
            StartConnect();
            StartReceive();
        }

        //
        //  每隔10秒检测一次客户端状态，如果客户端连接、并且客户端不为空则检测对应的状态
        //

        async private void Deamon()
        {
            while (true)
            {
                await Task.Delay( 10000 );
                if (m_Connected && m_Client != null)
                {
                    var send = m_Client.Poll( 3000, SelectMode.SelectRead );
                    var read = m_Client.Poll( 3000, SelectMode.SelectWrite );
                    if (send || read)
                    {
                        ConnectStateChanged?.Invoke( false );
                        m_Connected = false;
                    }
                }
            }
        }

        //
        //  Receive
        //
        private void StartReceive()
        {
            if (!m_Client.ReceiveAsync( m_Recv ))
            {
                try
                {
                    if (m_ContentLength == -1)
                    {
                        var data = new byte[ 16 ];
                        m_Read.Read( data, 0, data.Length );
                        EndReceive( new ArraySegment<byte>( data ) );

                    }
                    else
                    {
                        var data = new byte[ m_ContentLength ];
                        m_Read.Read( data, 0, data.Length );
                        EndReceive( new ArraySegment<byte>( data ) );
                    }
                }
                catch
                {
                    EndConnect();
                }
            }
        }

        private void EndReceive(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
            {
                var data = new byte[ e.BytesTransferred ];
                Array.Copy( e.Buffer, e.Offset, data, 0, data.Length );
                EndReceive( new ArraySegment<byte>( data ) );
            }
            else
            {
                EndConnect();
            }
        }

        private void EndReceive(ArraySegment<byte> data)//处理逻辑
        {

            if (m_ContentLength == -1)
            {
                if (HeaderProtocol.Validate( data ))
                {
                    var echo = new HeaderProtocol( data );
                    m_Version = echo.Version;
                    m_Protocol = echo.Protocol;
                    if (m_Version == 13 && m_Protocol == 0)
                    {
                        m_ContentLength = echo.ContentLength;
                        Echos( EchoFactory.ReplyClient( false, true ) );
                    }
                    else if (m_Version == 9 && m_Protocol == 0)
                    {
                        EndSend();
                    }
                }
            }
            else
            {
                if (m_ContentLength - data.Count < 0)
                {
                    var d = new ArraySegment<byte>( data.Array, data.Offset, m_ContentLength );
                    m_Buffer.AddRange( d );
                    m_ContentLength = 0;
                }
                else
                {
                    m_ContentLength -= data.Count;
                    m_Buffer.AddRange( data );
                }
                if (m_ContentLength == 0)
                {
                    ReceiveCompleted?.Invoke( m_Buffer.ToArray(), m_Protocol, m_Version );
                    m_Buffer.Clear();
                }
            }

            StartReceive();
        }

        private void StartSend(byte[] data)
        {
            try
            {
                var d = EchoFactory.PrepareReceive( data.Length ).ToArray();
                m_Send.Write( d, 0, d.Length );
                m_needSend = data;
            }
            catch
            {
                EndConnect();
            }
        }

        private void EndSend()
        {
            try
            {
                m_Send.Write( m_needSend, 0, m_needSend.Length );
                m_needSend = null;
            }
            catch
            {
                EndConnect();
            }
        }

        private void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    EndConnect( e );
                    break;
                case SocketAsyncOperation.Disconnect:
                    break;
                case SocketAsyncOperation.Receive:
                    EndReceive( e );
                    break;
                case SocketAsyncOperation.Send:
                    break;
            }
        }


        public void Dispose()
        {
            throw new NotImplementedException();
        }

        private void EndConnect()
        {
            m_lockConnected = false;
            m_Connected = false;
            ConnectStateChanged?.Invoke( false );
        }
        private void StartConnect()
        {
            m_Connected = true;
            ConnectStateChanged?.Invoke( true );
        }

        public void Send(byte[] data)
        {
            StartSend( data );
        }

        private void Echos(HeaderProtocol echo)
        {
            try
            {
                var data = echo.ToArray();
                m_Send.Write( data, 0, data.Length );
            }
            catch
            {
                EndConnect();
            }
        }

    }
}
